package com.easyfile.easyfilemaster.service.impl;

import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.symmetric.AES;
import cn.hutool.crypto.symmetric.DES;
import cn.hutool.crypto.symmetric.SymmetricAlgorithm;
import cn.hutool.crypto.symmetric.SymmetricCrypto;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.easyfile.easyfilemaster.entity.*;
import com.easyfile.easyfilemaster.entity.base.Result;
import com.easyfile.easyfilemaster.entity.base.ResultFactory;
import com.easyfile.easyfilemaster.entity.dto.*;
import com.easyfile.easyfilemaster.entity.vo.MultipartUploadVo;
import com.easyfile.easyfilemaster.exception.file.*;
import com.easyfile.easyfilemaster.exception.minio.MinioException;
import com.easyfile.easyfilemaster.mapper.FileFolderMapper;
import com.easyfile.easyfilemaster.mapper.FileMapper;
import com.easyfile.easyfilemaster.mapper.FolderMapper;
import com.easyfile.easyfilemaster.mapper.UserMapper;
import com.easyfile.easyfilemaster.service.FileService;
import com.easyfile.easyfilemaster.service.MinioService;
import com.easyfile.easyfilemaster.utils.CommonUtils;
import com.google.common.util.concurrent.AtomicDouble;
import io.minio.CreateMultipartUploadResponse;
import io.minio.ListPartsResponse;
import io.minio.ObjectWriteResponse;
import io.minio.messages.Part;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

/**
 * @author chenpeman
 * @date 2022年05月06日 16:15
 */
@Service
@Slf4j
public class FileServiceImpl implements FileService {
    @Resource
    FolderMapper folderMapper;
    @Resource
    FileFolderMapper fileFolderMapper;
    @Resource
    FileMapper fileMapper;
    @Resource
    UserMapper userMapper;
    @Resource
    MinioService minioService;
    @Resource
    RedisTemplate<String, Object> redisTemplate;
    @Override
    public boolean createDefaultFolder(Long userId) {
        //判断是否已经创建
        FolderDetail tempFolderDetail = folderMapper.selectOne(new LambdaQueryWrapper<FolderDetail>()
                .eq(FolderDetail::getUserId, userId));
        if (tempFolderDetail!=null){
            throw new DefaultFolderRepeatableCreatedException("该用户的根目录已创建");
        }

        //封装根目录对象
        FolderDetail folderDetail = FolderDetail.builder()
                .pid(0L)
                .name("根目录")
                .userId(userId)
                .folderNumber(0L)
                .fileNumber(0L)
                .fileCapacity(0.0)
                .build();

        //存入到数据库
        int row = folderMapper.insert(folderDetail);
        if (row!=1){
            throw new InsertFolderException("目录对象入库失败");
        }

        return true;
    }

    @Override
    public Result<FolderInfo> getDefaultFolder(GetDefaultFolderDto getDefaultFolderDto) {
        //获取根目录信息
        FolderDetail folderDetail = folderMapper.selectOne(new LambdaQueryWrapper<FolderDetail>()
                .eq(FolderDetail::getPid, getDefaultFolderDto.getPid())
                .eq(FolderDetail::getUserId, getDefaultFolderDto.getUserId()));

        if (folderDetail==null){
            throw new DefaultFolderNotFoundException("找不到该用户的根目录");
        }

        return ResultFactory.success(folderDetail.toFolderInfo(),"获取根目录文件成功");
    }

    @Override
    public Result<IPage<FolderInfo>> getChildFolders(GetChildFoldersDto getChildFoldersDto) {
        IPage<FolderInfo> page=new Page<>(getChildFoldersDto.getPage(),getChildFoldersDto.getSize());
        IPage<FolderInfo> childFolders = folderMapper.getChildFolders(page, new LambdaQueryWrapper<FolderDetail>()
                .eq(FolderDetail::getPid,getChildFoldersDto.getId())
                .eq(FolderDetail::getUserId,getChildFoldersDto.getUserId())
                .eq(FolderDetail::getDeleted,0));
        if (childFolders==null){
            throw new GetChildFoldersException("获取子文件夹失败");
        }
        return ResultFactory.success(childFolders,"查询成功");
    }

    @Override
    public Result<IPage<FileInfo>> getChildFiles(GetChildFilesDto getChildFilesDto) {
        IPage<FileInfo> page = new Page<>(getChildFilesDto.getPage(),getChildFilesDto.getSize());
        IPage<FileInfo> childFiles = fileMapper.getChildFiles(page, new QueryWrapper<>()
                .eq("file_folder.user_id",getChildFilesDto.getUserId())
                .eq("file_folder.folder_id",getChildFilesDto.getId())
                .eq("file.deleted",0)
                .eq("file_folder.deleted",0));
      if (childFiles==null){
          throw new GetChildFilesException("获取文件列表失败");
      }
        return ResultFactory.success(childFiles,"查询成功");
    }

    @Override
    public Result<IPage<FileInfo>> getFilesByTypeId(GetTypeFilesDto dto) {
        IPage<FileInfo> page = new Page<>(dto.getPage(),dto.getSize());
        IPage<FileInfo> childFiles = fileMapper.getChildFiles(page, new QueryWrapper<>()
                .eq("file_folder.user_id",dto.getUserId())
                .eq("file.type_id",dto.getTypeId())
                .eq("file.deleted",0)
                .eq("file_folder.deleted",0));
        if (childFiles==null){
            throw new GetTypeFileException("获取该分类的文件失败");
        }
        return ResultFactory.success(childFiles,"请求成功");
    }

    @Override
    public Result<IPage<FileInfo>> searchFiles(SearchFilesDto dto) {
        IPage<FileInfo> page = new Page<>(dto.getPage(),dto.getSize());
        IPage<FileInfo> files = fileMapper.getChildFiles(page, new QueryWrapper<>()
                .eq("file_folder.user_id", dto.getUserId())
                .like("file_folder.name", dto.getKey())
                .eq("file.deleted", 0)
                .eq("file_folder.deleted", 0));
        return ResultFactory.success(files,"搜索成功");
    }

    /**
     * 按关键字搜索某一类型的文件
     *
     * @param dto the dto
     * @return the result
     */
    @Override
    public Result<IPage<FileInfo>> searchTypeFiles(SearchTypeFilesDto dto) {
        IPage<FileInfo> page = new Page<>(dto.getPage(),dto.getSize());
        IPage<FileInfo> files = fileMapper.getChildFiles(page, new QueryWrapper<>()
                .eq("file_folder.user_id", dto.getUserId())
                .eq("file.typeId",dto.getTypeId())
                .like("file_folder.name", dto.getKey())
                .eq("file.deleted", 0)
                .eq("file_folder.deleted", 0));
        return ResultFactory.success(files,"搜索成功");
    }

    @Override
    public Result<IPage<FolderInfo>> searchFolders(SearchFoldersDto dto) {
        IPage<FolderInfo> page=new Page<>(dto.getPage(),dto.getSize());
        IPage<FolderInfo> folders = folderMapper.getChildFolders(page, new LambdaQueryWrapper<FolderDetail>()
                .like(FolderDetail::getName,dto.getKey())
                .eq(FolderDetail::getUserId,dto.getUserId())
                .eq(FolderDetail::getDeleted,0));
        return ResultFactory.success(folders,"搜索成功");
    }

    @Override
    public Result<List<String>> downloadFiles(DownloadFilesDto downloadFilesDto) {
        List<Long> ids = downloadFilesDto.getIds();
        Long userId = downloadFilesDto.getUserId();
        //去掉重复的请求数据
        LinkedHashMap<Long,String> fileMap = new LinkedHashMap<>(ids.size());
        //检验用户是否拥有该文件的下载权限
        ids.forEach(id->{
            FileFolder fileFolder = fileFolderMapper.selectOne(new LambdaQueryWrapper<FileFolder>()
                    .eq(FileFolder::getId, id)
                    .eq(FileFolder::getUserId, userId));
            if (fileFolder==null){
                throw new FileDoesNotBelongToUserException("文件不属于该用户");
            }
            //保存真实的文件id作为key，用户的文件名+类型作为value
            fileMap.put(fileFolder.getFileId(),fileFolder.getName()+"."+fileFolder.getType());
        });

        ArrayList<String> presignedUrls = new ArrayList<>(fileMap.size());
        fileMap.forEach((key,value)->{
            //通过文件id获取uri
            String uri = fileMapper.selectUriById(key);
            if (uri==null){
                throw new DownloadFilesException("获取文件uri失败");
            }

            //请求Minio临时下载连接
            String presignedUrl = minioService.getPresignedUrl(uri, value, TimeUnit.MINUTES, 5);
            if (presignedUrl==null){
                throw new MinioException("请求临时下载直链失败");
            }
            presignedUrls.add(presignedUrl);
        });

        return ResultFactory.success(presignedUrls,"下载成功");
    }

    @Override
    @Transactional
    public Result<Void> insertFolder(InsertFolderDto insertFolderDto) {

        //检查父文件夹是否存在
        FolderDetail parentFolderDetail = folderMapper.selectById(insertFolderDto.getPid());
        if (parentFolderDetail==null){
            throw new InsertFolderException("父文件夹不存在");
        }
        //检验父文件夹是否为该用户的
        if (!Objects.equals(parentFolderDetail.getUserId(), insertFolderDto.getUserId())){
            throw new InsertFolderException("该文件夹不属于此用户");
        }
        //检查有没有同名文件夹
        List<String> names = folderMapper.selectName(insertFolderDto.getPid(), insertFolderDto.getName());
        if (names.size()!=0){
            throw new InsertMultiFolderException("已存在同名的文件夹");
        }
        //封装文件夹对象
        FolderDetail folderDetail = FolderDetail
                .builder()
                .userId(insertFolderDto.getUserId())
                .pid(insertFolderDto.getPid())
                .name(insertFolderDto.getName())
                .fileNumber(0L)
                .folderNumber(0L)
                .fileCapacity(0.0)
                .build();
        //入库
        int row = folderMapper.insert(folderDetail);
        if (row!=1){
            throw new InsertFolderException("目录对象入库失败");
        }

        //修改父文件夹信息
        int row1 = folderMapper.update(parentFolderDetail, new UpdateWrapper<FolderDetail>()
                .setSql("folder_number = folder_number + 1")
                .lambda()
                .eq(FolderDetail::getId, insertFolderDto.getPid()));

        if (row1!=1){
            throw new UpdateFolderException("更新父文件夹数据失败");
        }
        return ResultFactory.success("新建成功");
    }

    @Override
    public Result<Void> renameFolder(RenameFolderDto renameFolderDto) {

        //判断文件夹是否存在
        FolderDetail folderDetail = folderMapper.selectById(renameFolderDto.getId());
        if (folderDetail==null){
            throw new  UpdateFolderException("文件夹不存在");
        }
        //判断该文件夹是否属于该用户
        if (!Objects.equals(folderDetail.getUserId(), renameFolderDto.getUserId())){
            throw new UpdateFolderException("父文件夹不属于该用户");
        }
        //判断父文件夹下是否包含同名文件
        FolderDetail multiFolderDetail = folderMapper.selectOne(new LambdaQueryWrapper<FolderDetail>()
                .eq(FolderDetail::getName, renameFolderDto.getName())
                .eq(FolderDetail::getPid, folderDetail.getPid())
                .eq(FolderDetail::getUserId, renameFolderDto.getUserId()));
        if (multiFolderDetail!=null){
            throw new InsertMultiFolderException("当前目录下已包含同名文件夹");
        }
        //修改文件名
        int row = folderMapper.update(folderDetail,new UpdateWrapper<FolderDetail>()
                .lambda()
                .set(FolderDetail::getName,renameFolderDto.getName())
                .eq(FolderDetail::getId,renameFolderDto.getId())
                .eq(FolderDetail::getUserId,renameFolderDto.getUserId()));

        if (row!=1){
            throw new UpdateFolderException("修改文件夹名失败");
        }
        return ResultFactory.success("修改成功");
    }

    @Override
    public Result<Void> renameFile(RenameFileDto renameFileDto) {

        //判断该文件是否存在
        FileFolder fileFolder = fileFolderMapper.selectById(renameFileDto.getId());
        if (fileFolder==null){
            throw new UpdateFileFolderException("该文件不存在");
        }
        //判断该文件是否属于此用户
        if (!Objects.equals(fileFolder.getUserId(), renameFileDto.getUserId())){
            throw new UpdateFileFolderException("该文件不属于此用户");
        }
        //判断文件夹下是否包含同名的文件
        FileFolder multiFileFolder = fileFolderMapper.selectOne(new LambdaQueryWrapper<FileFolder>()
                .eq(FileFolder::getFolderId, fileFolder.getFolderId())
                .eq(FileFolder::getName, renameFileDto.getName())
                .eq(FileFolder::getUserId, renameFileDto.getUserId()));
        if (multiFileFolder!=null){
            throw new InsertMultiFileException("当前文件夹下已包含同名文件");
        }
        //修改文件名
        int row = fileFolderMapper.update(fileFolder, new UpdateWrapper<FileFolder>()
                .lambda()
                .set(FileFolder::getName,renameFileDto.getName())
                .eq(FileFolder::getId,renameFileDto.getId())
                .eq(FileFolder::getUserId,renameFileDto.getUserId()));

        if (row!=1){
            throw new UpdateFileFolderException("重命名失败");
        }
        
        return ResultFactory.success("修改成功");
    }

    @Override
    @Transactional
    public Result<Void> deleteFiles(DeleteFilesDto dto) {
        List<Long> ids = dto.getIds();
        Long userId = dto.getUserId();
        //过滤掉重复的数据
        Set<Long> idsSet=new LinkedHashSet<>(ids);
        //遍历集合，删除记录
        idsSet.forEach(id->{
            //获取当前映射记录数据
            FileFolder fileFolder = fileFolderMapper.selectById(id);
            //判断是否为当前用户的数据
            if (!Objects.equals(fileFolder.getUserId(), userId)){
                throw new DeleteFileException("当前文件不属于该用户");
            }
            //修改文件数据
            Long fileId = fileFolder.getFileId();
            FileDetail fileDetail = fileMapper.selectById(fileId);
            fileDetail.setReferenced(fileDetail.getReferenced()-1);
            int row = fileMapper.updateById(fileDetail);
            if (row!=1){
                throw new DeleteFileException("更新源文件数据失败");
            }
            //修改所在文件夹数据
            Long folderId = fileFolder.getFolderId();
            FolderDetail folderDetail = folderMapper.selectById(folderId);
            folderDetail.setFileNumber(folderDetail.getFileNumber()-1);
            folderDetail.setFileCapacity(folderDetail.getFileCapacity()-fileDetail.getSize());
            int row1 = folderMapper.updateById(folderDetail);
            if (row1!=1){
                throw new DeleteFileException("更新所在文件夹数据失败");
            }
            //修改用户数据
            UserDetail userDetail = userMapper.selectById(userId);
            userDetail.setUsedCapacity(userDetail.getUsedCapacity()-fileDetail.getSize());
            int row2 = userMapper.updateById(userDetail);
            if (row2!=1){
                throw new DeleteFileException("更新用户数据失败");
            }
            //删除映射记录
            int row3 = fileFolderMapper.deleteById(id);
            if (row3!=1){
                throw new DeleteFileException("删除文件数据失败");
            }
        });
        return ResultFactory.success("删除成功");
    }

    @Override
    @Transactional
    public Result<Void> insertFileFolder(FileFolder fileFolder) {
        //检查用户是否存在
        UserDetail userDetail = userMapper.selectById(fileFolder.getUserId());
        if (userDetail==null){
            throw new InsertFileFolderException("不合法的用户id");
        }
        //检查文件夹是否存在
        FolderDetail folderDetail = folderMapper.selectOne(new LambdaQueryWrapper<FolderDetail>()
                .eq(FolderDetail::getId, fileFolder.getFolderId())
                .eq(FolderDetail::getUserId, fileFolder.getUserId()));
        if (folderDetail==null){
            throw new InsertFileFolderException("文件夹不存在");
        }
        //检查是否包含同名文件
        FileFolder sameNameFileFolder = fileFolderMapper.selectOne(new LambdaQueryWrapper<FileFolder>()
                .eq(FileFolder::getName, fileFolder.getName())
                .eq(FileFolder::getFolderId, fileFolder.getFolderId())
                .eq(FileFolder::getUserId, fileFolder.getUserId()));
        if (sameNameFileFolder!=null){
            throw new InsertFileFolderException("当前文件夹下包含同名文件");
        }

        //检查文件是否存在
        FileDetail fileDetail = fileMapper.selectById(fileFolder.getFileId());
        if (fileDetail==null){
            throw new InsertFileFolderException("文件不存在");
        }

        //判断用户剩余空间是否充足
        if (userDetail.getMaxCapacity()<(userDetail.getUsedCapacity()+fileDetail.getSize())){
            throw new InsertFileFolderException("用户剩余存储空间不足");
        }

        //插入数据库
        int row1 = fileFolderMapper.insert(fileFolder);
        if (row1!=1){
            throw new InsertFileFolderException("对象插入数据库失败");
        }

        //修改文件夹信息
        folderDetail.setFileNumber(folderDetail.getFileNumber()+1);
        folderDetail.setFileCapacity(folderDetail.getFileCapacity()+fileDetail.getSize());
        int i;
        do{
             i= folderMapper.updateById(folderDetail);
        }while (i!=1);

        //修改文件信息
        fileDetail.setReferenced(fileDetail.getReferenced()+1);
        int j;
        do {
            j=fileMapper.updateById(fileDetail);
        }while (j!=1);

        //修改用户信息
        userDetail.setUsedCapacity(userDetail.getUsedCapacity()+fileDetail.getSize());
        int k;
        do {
            k=userMapper.updateById(userDetail);
        }while (k!=1);

        return ResultFactory.success("加入成功");
    }



    @Override
    @Transactional
    public Result<Void> deleteFolders(DeleteFoldersDto dto) {
        List<Long> ids = dto.getIds();
        Long userId = dto.getUserId();
        Long pid = dto.getPid();
        //过滤掉重复的数据
        Set<Long> idsSet=new LinkedHashSet<>(ids);
        //删除文件夹
        idsSet.forEach(id->{
            //判断父文件夹下是否包含此文件夹
            FolderDetail folderDetail = folderMapper.selectOne(new LambdaQueryWrapper<FolderDetail>()
                    .eq(FolderDetail::getId, id)
                    .eq(FolderDetail::getPid, pid)
                    .eq(FolderDetail::getUserId, userId));
            if (folderDetail==null){
                throw new DeleteFolderException("当前目录下不包含此文件夹");
            }
            dfsDelete(id,userId);
        });
        //修改父文件夹数据
        FolderDetail folderDetail = folderMapper.selectById(pid);
        folderDetail.setFolderNumber(folderDetail.getFolderNumber()-idsSet.size());
        int row = folderMapper.updateById(folderDetail);
        if (row!=1){
            throw new DeleteFolderException("修改父文件夹数据失败");
        }

        return ResultFactory.success("删除成功");
    }

    @Override
    public Result<MultipartUploadVo> getPresignedChunkUrls(GetPresignedChunkUrlsDto dto) {
        //判断该文件是否已经上传(秒传)
        FileDetail fileDetail = fileMapper.selectOne(new LambdaQueryWrapper<FileDetail>()
                .eq(FileDetail::getMd5, dto.getMd5()));

        //解析文件名与类型
        String name = CommonUtils.getFileName(dto.getFileName());
        String type = CommonUtils.getFileType(dto.getFileName());
        //文件已经上传成功
        if (fileDetail!=null){
            //建立文件与当前目录的映射
            FileFolder fileFolder = FileFolder.builder()
                    .fileId(fileDetail.getId())
                    .folderId(dto.getFolderId())
                    .userId(dto.getUserId())
                    .name(name)
                    .type(type)
                    .build();
            insertFileFolder(fileFolder);

            MultipartUploadVo multipartUploadVo = new MultipartUploadVo();
            multipartUploadVo.setIsUploaded(true);
            return ResultFactory.success(multipartUploadVo,"上传成功");
        }

        //查询是否有分片上传记录
        //Object o = redisTemplate.opsForValue().get("upload:" + dto.getMd5());
        //有上传记录
        //if (o!=null){
        //    minioService.listMultipart(null,)
        //}

        //文件未上传

        //创建uri
        String uri = UUID.randomUUID().toString().replaceAll("-", "")+"."+type;
        //上传uploadId
        CreateMultipartUploadResponse response = minioService.uploadId(null, uri, null, null);
        if (response==null){
            throw new MinioException("请求上传Id失败");
        }
        String uploadId = response.result().uploadId();

        //缓存file对象与uploadId到redis，以md5作为key
        redisTemplate.opsForHash().put("upload:"+dto.getMd5(),"uri",uri);
        redisTemplate.opsForHash().put("upload:"+dto.getMd5(),"uploadId",uploadId);

        //创建签名
        Map<String, String> result = new HashMap<>();
        Map<String, String> reqParams = new HashMap<>();
        reqParams.put("uploadId",uploadId);
        for (int i = 1; i <=dto.getTotalChunks() ; i++) {
            reqParams.put("partNumber",String.valueOf(i));
            String uploadUrl = minioService.getPresignedObjectUrl(uri, reqParams);
            if (uploadUrl==null){
                throw new MinioException("请求上传地址失败");
            }
            result.put("chunk_"+(i-1),uploadUrl);
        }
        MultipartUploadVo multipartUploadVo = new MultipartUploadVo();
        multipartUploadVo.setPresignedChunksUrl(result);
        multipartUploadVo.setIsUploaded(false);
        multipartUploadVo.setUploadId(uploadId);
        return ResultFactory.success(multipartUploadVo,"获取成功");
    }

    @Override
    @Transactional
    public Result<Void> completeMultipartUpload(CompleteMultipartUploadDto dto) {
        //从redis中获取缓存数据
        String uri = (String) redisTemplate.opsForHash().get("upload:" + dto.getMd5(), "uri");
        String uploadId = (String) redisTemplate.opsForHash().get("upload:" + dto.getMd5(), "uploadId");
        //处理已上传的分片
        Part[] parts = new Part[10000];
        ListPartsResponse partResult = minioService.listMultipart(null, uri, 1000, 0, uploadId, null, null);
        if (partResult==null){
            throw new MinioException("获取分片失败");
        }
        int partNumber =1;
        for (Part part : partResult.result().partList()) {
            parts[partNumber - 1] = new Part(partNumber, part.etag());
            partNumber++;
        }
        //合并分片
        ObjectWriteResponse objectWriteResponse = minioService.completeMultipartUpload(null, uri, uploadId, parts, null, null);
        if (objectWriteResponse==null){
            throw new MinioException("合并分片失败");
        }

        //文件数据入库
        String fileName = dto.getFileName();
        String name = CommonUtils.getFileName(fileName);
        String type = CommonUtils.getFileType(fileName);
        Integer typeId = CommonUtils.getTypeId(type);
        FileDetail fileDetail = FileDetail.builder()
                .name(name)
                .type(type)
                .size(dto.getSize())
                .md5(dto.getMd5())
                .uri(uri)
                .userId(dto.getUserId())
                .referenced(0L)
                .typeId(typeId)
                .build();
        int row= fileMapper.insert(fileDetail);
        if (row!=1){
            throw new InsertFileException("文件数据入库失败");
        }

        //建立文件与文件夹的映射
        FileFolder fileFolder = FileFolder.builder()
                .fileId(fileDetail.getId())
                .folderId(dto.getFolderId())
                .userId(dto.getUserId())
                .name(name)
                .type(type)
                .build();

        insertFileFolder(fileFolder);

        return ResultFactory.success("上传成功");

    }

    @Override
    @Transactional
    public Result<Void> moveFiles(MoveFilesDto dto) {
        //判断原文件夹与目标文件夹是否一致
        Long fromFolderId = dto.getFromFolderId();
        Long toFolderId = dto.getToFolderId();
        if (fromFolderId.equals(toFolderId)){
            throw new MoveFilesException("请勿移动文件到同一文件夹");
        }
        //检查目标文件夹是否存在
        FolderDetail toFolder = folderMapper.selectOne(new LambdaQueryWrapper<FolderDetail>()
                .eq(FolderDetail::getId, toFolderId)
                .eq(FolderDetail::getUserId, dto.getUserId()));
        if (toFolder==null){
            throw new MoveFilesException("目标文件夹不存在");
        }
        //获取原文件夹数据
        FolderDetail fromFolder = folderMapper.selectOne(new LambdaQueryWrapper<FolderDetail>()
                .eq(FolderDetail::getId, fromFolderId)
                .eq(FolderDetail::getUserId, dto.getUserId()));
        if (fromFolder==null){
            throw new MoveFilesException("原文件夹不存在");
        }
        //处理重复的数据
        List<Long> ids = dto.getIds();
        Set<Long> idsSet = new HashSet<>(ids);

        idsSet.forEach(id->{
            //获取目标文件映射数据
            FileFolder fileFolder = fileFolderMapper.selectById(id);
            if (fileFolder==null){
                throw new MoveFilesException("源文件映射不存在");
            }
            //获取文件数据
            FileDetail fileDetail = fileMapper.selectById(fileFolder.getFileId());
            if (fileDetail==null){
                throw new MoveFilesException("源文件不存在");
            }
            //判断目标文件夹中是否有重名文件
            FileFolder sameNameFileFolder = fileFolderMapper.selectOne(new LambdaQueryWrapper<FileFolder>()
                    .eq(FileFolder::getName, fileFolder.getName())
                    .eq(FileFolder::getUserId, fileFolder.getUserId())
                    .eq(FileFolder::getFolderId, toFolderId));
            if (sameNameFileFolder!=null){
                throw new MoveFilesException("目标文件夹内包含同名文件");
            }
            //修改所属的文件夹
            fileFolder.setFolderId(toFolderId);
            int row = fileFolderMapper.updateById(fileFolder);
            if (row!=1){
                throw new MoveFilesException("修改目录失败");
            }
            //修改原文件夹数据
            fromFolder.setFileNumber(fromFolder.getFileNumber()-1);
            fromFolder.setFileCapacity(fromFolder.getFileCapacity()-fileDetail.getSize());

            //修改目标文件夹数据
            toFolder.setFileNumber(toFolder.getFileNumber()+1);
            toFolder.setFileCapacity(toFolder.getFileCapacity()+fileDetail.getSize());

        });

        //更新原文件夹
        int row = folderMapper.updateById(fromFolder);
        if (row!=1){
            throw new MoveFilesException("更新原文件夹数据失败");
        }

        //更新目标文件夹
        int row1 = folderMapper.updateById(toFolder);
        if (row1!=1){
            throw new MoveFilesException("更新目标文件夹数据失败");
        }

        return ResultFactory.success("粘贴成功");
    }

    @Override
    @Transactional(isolation = Isolation.READ_COMMITTED)
    public Result<Void> copyFiles(CopyFilesDto dto) {
        //判断原文件夹与目标文件夹是否一致
        Long fromFolderId = dto.getFromFolderId();
        Long toFolderId = dto.getToFolderId();
        if (fromFolderId.equals(toFolderId)){
            throw new CopyFilesException("请勿复制文件到同一文件夹");
        }
        //判断目标文件夹是否存在
        FolderDetail toFolder = folderMapper.selectOne(new LambdaQueryWrapper<FolderDetail>()
                .eq(FolderDetail::getId, toFolderId)
                .eq(FolderDetail::getUserId, dto.getUserId()));
        if (toFolder==null){
            throw new CopyFilesException("目标文件夹不存在");
        }
        //获取用户数据
        UserDetail userDetail = userMapper.selectById(dto.getUserId());
        if (userDetail==null){
            throw new MoveFilesException("非法的用户");
        }
        //判断容量是否充足
        List<Long> ids = dto.getIds();
        Map<Long, FileFolder> fileFolderMap=new HashMap<>(ids.size());
        Map<Long, FileDetail> fileDetailMap=new HashMap<>(ids.size());
        AtomicDouble totalSize=new AtomicDouble(0.0);
        ids.forEach(id->{
            if (!fileFolderMap.containsKey(id)){
                //判断文件映射是否存在
                FileFolder fileFolder = fileFolderMapper.selectById(id);
                if (fileFolder==null){
                    throw new CopyFilesException("文件映射不存在");
                }
                //判断目标文件夹内是否有同名文件
                FileFolder sameNameFileFolder = fileFolderMapper.selectOne(new LambdaQueryWrapper<FileFolder>()
                        .eq(FileFolder::getName, fileFolder.getName())
                        .eq(FileFolder::getUserId, fileFolder.getUserId())
                        .eq(FileFolder::getFolderId, toFolderId));
                if (sameNameFileFolder!=null){
                    throw new CopyFilesException("目标文件夹内包含同名文件");
                }
                fileFolderMap.put(id,fileFolder);
                //判断源文件是否存在
                FileDetail fileDetail = fileMapper.selectById(fileFolder.getFileId());
                if (fileDetail==null){
                    throw new CopyFilesException("源文件不存在");
                }
                totalSize.addAndGet(fileDetail.getSize());
                fileDetailMap.put(fileDetail.getId(), fileDetail);
            }
        });
        if (userDetail.getMaxCapacity()<(userDetail.getUsedCapacity()+totalSize.doubleValue())){
            throw new CopyFilesException("用户剩余存储空间不足");
        }

        //新增文件映射
        fileFolderMap.forEach((k,v)->{
            FileFolder fileFolder = FileFolder.builder()
                    .fileId(v.getFileId())
                    .folderId(toFolderId)
                    .userId(dto.getUserId())
                    .name(v.getName())
                    .type(v.getType()).build();
            int row = fileFolderMapper.insert(fileFolder);
            if (row!=1){
                throw new CopyFilesException("插入文件映射失败");
            }
        });

        //修改原文件数据
        fileDetailMap.forEach((k,v)->{
            v.setReferenced(v.getReferenced()+1);
            int row;
            int count=0;
            do {
                row= fileMapper.updateById(v);
                count++;
            }while (row!=1&&count!=100);
            if (row!=1){
                throw new CopyFilesException("修改源文件数据失败");
            }
        });

        //修改目标文件夹数据
        toFolder.setFileCapacity(toFolder.getFileCapacity()+totalSize.doubleValue());
        toFolder.setFileNumber(toFolder.getFileNumber()+ids.size());
        int row = folderMapper.updateById(toFolder);
        if (row!=1){
            throw new CopyFilesException("修改目标文件夹数据失败");
        }

        //修改用户数据
        userDetail.setUsedCapacity(userDetail.getUsedCapacity()+totalSize.doubleValue());
        int row1 = userMapper.updateById(userDetail);
        if (row1!=1){
            throw new CopyFilesException("修改用户数据失败");
        }

        return ResultFactory.success("粘贴成功");
    }

    @Resource
    private DES des;

    /**
     * 批量分享文件
     *
     * @param dto the dto
     * @return the result
     */
    @Override
    public Result<String> shareFiles(ShareFilesDto dto) {
        //去重
        List<Long> ids = dto.getIds();
        Set<Long> idsSet=new HashSet<>(ids);
        //遍历文件id
        List<FileFolder> fileFolders=new ArrayList<>(idsSet.size());
        idsSet.forEach(id->{
            //检查文件是否存在
            FileFolder fileFolder = fileFolderMapper.selectOne(new LambdaQueryWrapper<FileFolder>()
                    .eq(FileFolder::getId, id)
                    .eq(FileFolder::getUserId, dto.getUserId()));
            if (fileFolder==null){
                throw new ShareFileException("找不到对应文件");
            }
            fileFolders.add(fileFolder);
        });

        //生成对称加密密钥
        //String shareKey = des.encryptHex(builder.toString());

        //生成UUID
        String shareKey = IdUtil.simpleUUID();
        //文件信息缓存至redis
        redisTemplate.opsForValue().set("shareKey:"+shareKey,fileFolders,dto.getTimeout(),TimeUnit.MILLISECONDS);

        return ResultFactory.success(shareKey,"分享成功");
    }

    /**
     * 通过密匙保存分享文件
     *
     * @param dto the dto
     * @return the result
     */
    @Override
    @Transactional
    public Result<Void> saveShareFiles(SaveShareFilesDto dto) {
        //检查密钥是否合法
        //String s = des.decryptStr(dto.getShareKey());

        //检查redis中是否有shareKey
        Object o = redisTemplate.opsForValue().get("shareKey:" + dto.getShareKey());
        if (o==null){
            throw new SaveShareFilesException("分享文件已过期或密钥错误");
        }
        List<FileFolder> fileFolders= (List<FileFolder>) o;
        //保存文件
        fileFolders.forEach(fileFolder -> {
            FileFolder newFileFolder = FileFolder.builder()
                    .fileId(fileFolder.getFileId())
                    .folderId(dto.getFolderId())
                    .userId(dto.getUserId())
                    .name(fileFolder.getName())
                    .type(fileFolder.getType())
                    .build();
            insertFileFolder(newFileFolder);
        });

        return ResultFactory.success("保存成功");
    }

    /**
     * 递归删除文件夹中的文件
     *
     * @param folderId the folder id
     */
    @Transactional
    public void dfsDelete(Long folderId,Long userId){

        //获取该目录下的所有文件夹id
        List<Long> childFolderIds = folderMapper.selectFolderIds(new LambdaQueryWrapper<FolderDetail>()
                .eq(FolderDetail::getPid, folderId)
                .eq(FolderDetail::getUserId, userId)
                .eq(FolderDetail::getDeleted,0));

        //深度遍历子文件夹
        if (childFolderIds!=null&&childFolderIds.size()!=0){
            childFolderIds.forEach(id->{
                dfsDelete(id,userId);
            });
        }

        //获取该用户当前目录下的所有文件
        List<Long> fileFolderIds = fileFolderMapper.selectIds(new LambdaQueryWrapper<FileFolder>()
                .eq(FileFolder::getFolderId, folderId)
                .eq(FileFolder::getUserId, userId)
                .eq(FileFolder::getDeleted,0));

        //如果当前目录下存在文件，则进行删除
        if (fileFolderIds!=null&&fileFolderIds.size()!=0){
            //删除该目录下的这些文件
            DeleteFilesDto deleteFilesDto = new DeleteFilesDto();
            deleteFilesDto.setUserId(userId);
            deleteFilesDto.setIds(fileFolderIds);
            deleteFiles(deleteFilesDto);
        }

        //删除此文件夹记录
        int row = folderMapper.deleteById(folderId);
        if (row!=1){
            throw new DeleteFolderException("删除该文件夹记录失败");
        }
    }

}
